/**
 * @file main.c
 * @brief 文件监控程序 - 使用inotify监控文件和目录的变更
 *
 * 该程序使用Linux inotify API来监控指定目录和文件的变更，
 * 支持递归监控新建的文件夹及其内容，并将变更记录保存到SQLite数据库中。
 *
 * 功能特性：
 * - 实时监控文件的创建、修改、删除和移动
 * - 支持递归监控目录结构
 * - 自动为新建的文件夹添加监控
 * - 将事件记录到SQLite数据库
 * - 实时输出事件信息到控制台
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <sys/time.h>
#include <dirent.h>
#include <errno.h>
#include <time.h>
#include <sqlite3.h>
#include <getopt.h>
#include <limits.h>

#ifndef PATH_MAX
#define PATH_MAX 4096  // 系统路径最大长度
#endif

// inotify相关常量定义
#define EVENT_SIZE (sizeof(struct inotify_event))        // 单个事件大小
#define BUF_LEN (1024 * (EVENT_SIZE + 16))              // 事件缓冲区大小
#define MAX_WATCHES 1024                                // 最大监控数量
#define MAX_PATHS 1024                                  // 最大路径数量

// 监控路径结构体
typedef struct {
    char path[PATH_MAX];  // 监控路径
    int is_dir;           // 是否为目录
} watch_path_t;

// 事件类型枚举
typedef enum {
    EVENT_ADD,  // 文件/目录添加
    EVENT_MOD,  // 文件/目录修改
    EVENT_DEL   // 文件/目录删除
} event_type_t;

// 全局变量定义
static sqlite3 *db = NULL;                              // 数据库连接
static int inotify_fd = -1;                            // inotify文件描述符
static watch_path_t watch_paths[MAX_PATHS];            // 监控路径数组
static int path_count = 0;                             // 当前监控路径数量
static int wd_to_path[MAX_WATCHES];                    // watch descriptor到路径索引的映射
static char db_filename[PATH_MAX];                     // 数据库文件名

// 事件类型字符串数组，用于输出
static const char* event_type_str[] = {
    "ADD", "MOD", "DEL"
};

/**
 * @brief 生成默认的数据库文件名（基于当前时间戳）
 * @param buffer 存储生成的文件名的缓冲区
 * @param buffer_size 缓冲区大小
 */
static void generate_default_db_filename(char *buffer, size_t buffer_size) {
    time_t now = time(NULL);
    struct tm *tm_info = localtime(&now);

    // 格式：YYYY_MM_DD_HH_MM_SS.db
    strftime(buffer, buffer_size - 4, "%Y_%m_%d_%H_%M_%S", tm_info);
    strcat(buffer, ".db");
}

/**
 * @brief 打印程序使用说明
 * @param prog_name 程序名称
 */
static void print_usage(const char *prog_name) {
    printf("Usage: %s [-f database.db] [-h] <path1> [path2] [path3] ...\n", prog_name);
    printf("  -f, --database  Specify database filename (default: YYYY_MM_DD_HH_MM_SS.db)\n");
    printf("  -h, --help      Show this help message\n");
    printf("  paths           Directories or files to monitor\n");
}

/**
 * @brief 初始化数据库
 * @param db_path 数据库文件路径
 * @return 成功返回0，失败返回-1
 */
static int init_database(const char *db_path) {
    char *err_msg = NULL;
    // 创建文件事件表，用于存储文件变更记录
    const char *sql = "CREATE TABLE IF NOT EXISTS file_events ("
                      "id INTEGER PRIMARY KEY AUTOINCREMENT,"
                      "timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,"
                      "event_type TEXT NOT NULL,"
                      "file_path TEXT NOT NULL"
                      ");";

    // 打开数据库连接
    int rc = sqlite3_open(db_path, &db);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
        return -1;
    }

    // 执行建表SQL
    rc = sqlite3_exec(db, sql, NULL, NULL, &err_msg);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "SQL error: %s\n", err_msg);
        sqlite3_free(err_msg);
        sqlite3_close(db);
        return -1;
    }

    return 0;
}

/**
 * @brief 插入文件事件到数据库并打印到控制台
 * @param event_type 事件类型（ADD、MOD、DEL）
 * @param file_path 文件路径
 * @return 成功返回0，失败返回-1
 */
static int insert_event(event_type_t event_type, const char *file_path) {
    sqlite3_stmt *stmt;
    const char *sql = "INSERT INTO file_events (event_type, file_path) VALUES (?, ?);";

    // 准备SQL语句
    int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
        return -1;
    }

    // 绑定参数
    sqlite3_bind_text(stmt, 1, event_type_str[event_type], -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 2, file_path, -1, SQLITE_STATIC);

    // 执行插入操作
    rc = sqlite3_step(stmt);
    if (rc != SQLITE_DONE) {
        fprintf(stderr, "Failed to insert event: %s\n", sqlite3_errmsg(db));
        sqlite3_finalize(stmt);
        return -1;
    }

    sqlite3_finalize(stmt);

    // 获取当前时间戳并格式化
    time_t now = time(NULL);
    struct tm *tm_info = localtime(&now);
    char timestamp[64];
    strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info);

    // 打印到控制台
    printf("%s | %s | %s\n", timestamp, event_type_str[event_type], file_path);
    fflush(stdout);

    return 0;
}

/**
 * @brief 为指定路径添加inotify监控
 * @param path 要监控的路径
 * @param path_index 路径在watch_paths数组中的索引
 * @return 成功返回watch descriptor，失败返回-1
 */
static int add_watch_to_path(const char *path, int path_index) {
    // 添加inotify监控，监听文件创建、修改、删除、移动事件
    int wd = inotify_add_watch(inotify_fd, path,
                               IN_CREATE | IN_MODIFY | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO);
    if (wd == -1) {
        fprintf(stderr, "Cannot watch '%s': %s (errno: %d)\n", path, strerror(errno), errno);
        if (errno == EACCES) {
            fprintf(stderr, "Permission denied. Try running with sudo or check directory permissions.\n");
        }
        return -1;
    } else {
        printf("Added watch for '%s' (wd: %d)\n", path, wd);
        // 建立watch descriptor到路径索引的映射
        if (wd < MAX_WATCHES) {
            wd_to_path[wd] = path_index;
        }
        return wd;
    }
}

/**
 * @brief 递归地为路径添加监控，支持监控新建的文件夹及其内容
 * @param path 要监控的路径
 * @return 成功返回0，失败返回-1
 */
static int add_watch_recursive(const char *path) {
    DIR *dir;
    struct dirent *entry;
    struct stat statbuf;
    char full_path[PATH_MAX];
    int current_path_index = -1;

    // 检查该路径是否已经在watch_paths中
    for (int i = 0; i < path_count; i++) {
        if (strcmp(watch_paths[i].path, path) == 0) {
            current_path_index = i;
            break;
        }
    }

    // 如果没有找到，将其添加到watch_paths数组中
    if (current_path_index == -1) {
        if (path_count >= MAX_PATHS) {
            fprintf(stderr, "Maximum number of paths reached\n");
            return -1;
        }

        // 安全地复制路径字符串
        strncpy(watch_paths[path_count].path, path, PATH_MAX - 1);
        watch_paths[path_count].path[PATH_MAX - 1] = '\0';

        // 获取文件状态信息
        if (stat(path, &statbuf) == 0) {
            watch_paths[path_count].is_dir = S_ISDIR(statbuf.st_mode);
        } else {
            fprintf(stderr, "Cannot stat '%s': %s\n", path, strerror(errno));
            return -1;
        }

        current_path_index = path_count;
        path_count++;
    }

    // 为当前路径添加监控
    int wd = add_watch_to_path(path, current_path_index);
    if (wd == -1) {
        return -1;
    }

    // 如果是目录，递归地为所有子目录添加监控
    if (stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) {
        dir = opendir(path);
        if (dir == NULL) {
            fprintf(stderr, "Cannot open directory '%s': %s\n", path, strerror(errno));
            return -1;
        }

        // 遍历目录中的所有条目
        while ((entry = readdir(dir)) != NULL) {
            // 跳过当前目录和上级目录
            if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
                continue;
            }

            // 构建完整的子路径
            snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name);

            // 如果是子目录，递归添加监控
            if (stat(full_path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) {
                add_watch_recursive(full_path);
            }
        }

        closedir(dir);
    }

    return 0;
}

/**
 * @brief 添加初始监控路径
 * @param path 要监控的路径
 * @return 成功返回0，失败返回-1
 */
static int add_path(const char *path) {
    struct stat statbuf;
    // 获取文件状态信息
    if (stat(path, &statbuf) != 0) {
        fprintf(stderr, "Cannot stat '%s': %s\n", path, strerror(errno));
        return -1;
    }

    // 检查路径是否已存在于watch_paths中
    for (int i = 0; i < path_count; i++) {
        if (strcmp(watch_paths[i].path, path) == 0) {
            return 0; // 已存在，直接返回
        }
    }

    // 检查是否达到最大路径数量限制
    if (path_count >= MAX_PATHS) {
        fprintf(stderr, "Maximum number of paths reached\n");
        return -1;
    }

    // 安全地复制路径字符串
    strncpy(watch_paths[path_count].path, path, PATH_MAX - 1);
    watch_paths[path_count].path[PATH_MAX - 1] = '\0';
    watch_paths[path_count].is_dir = S_ISDIR(statbuf.st_mode);
    path_count++;

    return 0;
}

/**
 * @brief 处理inotify事件，包括文件创建、修改、删除等
 */
static void handle_events() {
    char buffer[BUF_LEN];
    int length, i = 0;

    // 从inotify读取事件数据
    length = read(inotify_fd, buffer, BUF_LEN);
    if (length < 0) {
        if (errno != EAGAIN) {
            perror("read");
        }
        return;
    }

    // 遍历处理所有事件
    while (i < length) {
        struct inotify_event *event = (struct inotify_event *) &buffer[i];

        if (event->len > 0) {
            char full_path[PATH_MAX] = {0};

            // 根据watch descriptor找到对应的监控路径
            if (event->wd < MAX_WATCHES && wd_to_path[event->wd] >= 0) {
                int path_idx = wd_to_path[event->wd];

                // 安全地复制基础路径
                strncpy(full_path, watch_paths[path_idx].path, sizeof(full_path) - 1);
                full_path[sizeof(full_path) - 1] = '\0';

                // 如果路径末尾没有斜杠，添加一个
                size_t path_len = strlen(full_path);
                if (path_len > 0 && full_path[path_len - 1] != '/' && path_len < sizeof(full_path) - 1) {
                    full_path[path_len] = '/';
                    full_path[path_len + 1] = '\0';
                    path_len++;
                }

                // 安全地追加文件名
                size_t remaining = sizeof(full_path) - path_len - 1;
                if (remaining > 0 && strlen(event->name) > 0) {
                    strncat(full_path, event->name, remaining);
                }
            } else {
                // 如果找不到对应的路径，直接使用文件名作为后备方案
                strncpy(full_path, event->name, sizeof(full_path) - 1);
                full_path[sizeof(full_path) - 1] = '\0';
            }

            event_type_t event_type;

            // 处理文件创建或移动事件
            if (event->mask & (IN_CREATE | IN_MOVED_TO)) {
                event_type = EVENT_ADD;
                insert_event(event_type, full_path);

                // 如果是新创建的目录，为其添加递归监控
                if (event->mask & IN_ISDIR) {
                    add_watch_recursive(full_path);
                }
            } else if (event->mask & IN_MODIFY) {
                // 处理文件修改事件
                event_type = EVENT_MOD;
                insert_event(event_type, full_path);
            } else if (event->mask & (IN_DELETE | IN_MOVED_FROM)) {
                // 处理文件删除或移出事件
                event_type = EVENT_DEL;
                insert_event(event_type, full_path);
            }
        }

        // 移动到下一个事件
        i += EVENT_SIZE + event->len;
    }
}

/**
 * @brief 主函数 - 文件监控程序的入口点
 * @param argc 命令行参数个数
 * @param argv 命令行参数数组
 * @return 程序退出码
 */
int main(int argc, char *argv[]) {
    // 检查命令行参数
    if (argc < 2) {
        print_usage(argv[0]);
        return 1;
    }

    // 解析命令行参数
    static struct option long_options[] = {
        {"database", required_argument, 0, 'f'},
        {"help", no_argument, 0, 'h'},
        {0, 0, 0, 0}
    };

    int option_index = 0;
    int c;
    int have_db_filename = 0;

    while ((c = getopt_long(argc, argv, "f:h", long_options, &option_index)) != -1) {
        switch (c) {
            case 'f':
                // 指定了数据库文件名
                strncpy(db_filename, optarg, PATH_MAX - 1);
                db_filename[PATH_MAX - 1] = '\0';
                have_db_filename = 1;
                break;
            case 'h':
                print_usage(argv[0]);
                return 0;
            case '?':
                return 1;
            default:
                break;
        }
    }

    // 如果没有指定数据库文件名，生成默认名称
    if (!have_db_filename) {
        generate_default_db_filename(db_filename, sizeof(db_filename));
    }

    // 将剩余参数作为要监控的路径
    if (optind >= argc) {
        fprintf(stderr, "Error: No paths specified to monitor\n");
        print_usage(argv[0]);
        return 1;
    }

    for (int i = optind; i < argc; i++) {
        if (add_path(argv[i]) != 0) {
            return 1;
        }
    }

    // 初始化数据库
    printf("Using database: %s\n", db_filename);
    if (init_database(db_filename) != 0) {
        return 1;
    }

    // 初始化inotify实例
    inotify_fd = inotify_init();
    if (inotify_fd == -1) {
        perror("inotify_init");
        sqlite3_close(db);
        return 1;
    }

    // 初始化watch descriptor到路径的映射数组
    for (int i = 0; i < MAX_WATCHES; i++) {
        wd_to_path[i] = -1;
    }

    // 为所有路径添加监控
    for (int i = 0; i < path_count; i++) {
        if (add_watch_recursive(watch_paths[i].path) != 0) {
            close(inotify_fd);
            sqlite3_close(db);
            return 1;
        }
        printf("Monitoring: %s\n", watch_paths[i].path);
    }

    printf("File monitor started. Press Ctrl+C to stop.\n");

    // 主事件循环
    while (1) {
        fd_set readfds;
        struct timeval timeout;

        // 设置文件描述符集
        FD_ZERO(&readfds);
        FD_SET(inotify_fd, &readfds);

        // 设置超时时间
        timeout.tv_sec = 1;
        timeout.tv_usec = 0;

        // 等待inotify事件
        int ready = select(inotify_fd + 1, &readfds, NULL, NULL, &timeout);
        if (ready == -1) {
            if (errno == EINTR) {
                continue; // 被信号中断，继续循环
            }
            perror("select");
            break;
        } else if (ready > 0) {
            // 有事件到达，处理事件
            if (FD_ISSET(inotify_fd, &readfds)) {
                handle_events();
            }
        }
    }

    // 清理资源
    close(inotify_fd);
    sqlite3_close(db);

    return 0;
}